#pragma warning(disable : 4996)
#include "InterOpWriter.h"
#include "SPAIopAcisOutputPart.h"
#include "SPAIopOutputPSInstance.h"
#include "SPAIopOutputPSReference.h"
#include "SPAIopPartExporter.h"
#include "SPAIopProductStructureExporter.h"
#include "SPAIopWString.h"
#include "../InterOpUtils.h"
#include "common/logger.h"
#include "kernapi.hxx"
#include "lists.hxx"
#include "mmgr.hxx"
#include "utils/FileUtil.h"
#include "../InterOpUtils.h"
#include "SPAIopOutputProductStructure.h"
#include "SPAIopResult.h"
#include "SPAIopPolicy.h"
#include "../InterOpUtils.h"
#include "body.hxx"
#include "api.hxx"
#include <cstrapi.hxx>
#include <topology/TopoCast.hpp>
#include <topology/TopoCompound.hpp>
#include <topology/TopoExplorer.hpp>
#include <topology/TopoIterator.hpp>
#include <topology/TopologyMacros.hpp>
#include <unordered_map>
#include "InterOpWriterHelper.h"
#include "InterOpDocumentInstance.h"
#include "InterOpDocumentReference.h"

AMCAX_EXCHANGE_NAMESPACE_BEGIN

namespace InterOp
{

    Writer::Writer()
    {
    }

    void Writer::WriterToFile(std::string filePath, const std::vector<std::shared_ptr<InterOpObject>> &exchangeShapes)
    {
        if (FileUtil::IsFileExist(filePath))
        {
            LOG_ERROR("file<{}> already exists.", filePath);
            return;
        }

        std::string fileExtension = FileUtil::GetExtension(filePath);

        SPAIopWString wTargetFilePath = InterOp::StringToSPAIopWString(filePath);

        // Create and set Exporter.
        SPAIopPartExporter partExporter;

        // Create a Part Data object
        SPAIopAcisOutputPart partData;

        ENTITY_LIST acisEntitys;
        // BODY * body ;
        // api_make_cuboid(20.0,20.0,20.0,body) ;
        // acisEntitys.add(body) ;
        ConvertObject(exchangeShapes, acisEntitys);
        // set the ENTITYs to the Part Data object
        partData.SetPartEntities(acisEntitys);

        // create the policy object and add any options required.
        SPAIopPolicy policy;
        try
        {
            SPAIopResult res = partExporter.ExportWithPolicy(policy, partData, wTargetFilePath);
        }
        catch (SPAIopAcisError &error)
        {
            // error thrown in case of fatal outcome
            // UserDoSomethingWithError(error);
        }

        // TODO: release entity
        for (auto entity : acisEntitys)
        {
            api_del_entity(entity);
        }
    }

    void Writer::ConvertObject(const std::vector<std::shared_ptr<InterOpObject>> &exchangeShapes, ENTITY_LIST &entityList)
    {

        for (auto &object : exchangeShapes)
        {
            if (object->shape.IsNull())
            {
                continue;
            }
            BODY *body = ACIS_NEW BODY;
            WriterConvert::Convert(object->shape, body, nullptr);
            if (!body)
            {
                LOG_ERROR("body is null.");
                continue;
            }
            entityList.add(body);
        }
    }

    void Writer::WriterToFile(std::string filePath, ENTITY_LIST entityList)
    {
        // Set the units and product_id.
        FileInfo fileinfo;
        fileinfo.set_units(1.0);
        fileinfo.set_product_id("Example Application");
        outcome result = api_set_file_info((FileIdent | FileUnits), fileinfo);

        // Also set the option to produce sequence numbers in the SAT file.
        result = api_set_int_option("sequence_save_files", 1);
        // check_outcome(result);

        // Open a file for writing, save the list of entities, and close the file.
        FILE *save_file = acis_fopen(filePath.c_str(), "wb");
        result = api_save_entity_list(save_file, TRUE, entityList);
        acis_fclose(save_file);
        // check_outcome(result);
    }

    void Writer::ConvertObject2(const std::shared_ptr<InterOpDocumentReference> &docRootReference, ENTITY_LIST &entityList)
    {
        if(!docRootReference->IsPart())
        {
            return;
        }
        AMCAX::TopoCompound partShape;
        docRootReference->GetPartShape(partShape);

        //////////////////////////////////////////////////////////
        /// For testing.
        // // AMCAX::Point3 origin;
        // // AMCAX::Direction3 zdir(0.0, 0.0, 1.0), xdir(1.0, 0.0, 0.0), ydir(0.0, 1.0, 0.0);
        // AMCAX::Transformation3 trans;
        // trans.SetValues(
        //     0.0, 1.0, 0.0, 0.0, 
        //     1.0, 0.0, 0.0, 0.0, 
        //     0.0, 0.0, 1.0, 0.0);        
        // AMCAX::TopoLocation loc(trans);
        // partShape.Move(loc);
        // auto loc = partShape.Location();
        // if(loc.IsIdentity())
        // {
        //     auto transf = loc.Transformation();
        //     partShape.SetLocation(loc);
        // }
        //////////////////////////////////////////////////////////

        ENTITY *entity = ACIS_NEW BODY;
        WriterConvert::Convert(partShape, entity, nullptr);
        if (!entity)
        {
            LOG_ERROR("entity is null.");
        }
        entityList.add(entity);
    }

    void Writer::ExportInstanceToOutputProductStructureRecursion(const std::shared_ptr<InterOpDocumentInstance> &docInstance, SPAIopOutputProductStructure& oOutputPS, SPAIopOutputPSReference& outputReference)
    {
        auto docRef = docInstance->GetDocumentReference();
        if (!docRef)
        {
            LOG_ERROR("document reference is null.");
            return;
        }

        if(docRef->IsPart())
        {
            // Create a Part Data object
            SPAIopAcisOutputPart partData;
            ENTITY_LIST entityList;
            ConvertObject2(docRef, entityList);
            // set the ENTITYs to the Part Data object
            partData.SetPartEntities(entityList);
            outputReference.SetOutputPartData(partData);

            // ////////////////////////////////////////////////////////////////////////////////
            // /// For testing
            // // create the policy object and add any options required.
            // SPAIopPolicy policy;
            // try
            // {
            //     SPAIopPartExporter partExporter;
            //     SPAIopWString path = StringToSPAIopWString("D:\\amcax_dev\\modelFiles-Test\\debug\\step\\"+ docInstance->GetName() + ".step");
            //     SPAIopResult res = partExporter.ExportWithPolicy(policy, partData, path);
            // }
            // catch (SPAIopAcisError &error)
            // {
            //     // error thrown in case of fatal outcome
            //     // UserDoSomethingWithError(error);
            // }
            // ///////////////////////////////////////////////////////////////////////////////

            // TODO: set PMI, mapping data

            // // Release created entities.
            // for (auto* entity : entityList)
            // {
            //     api_del_entity(entity);
            // }
        }
        else
        {
            // Build child instances and references to output product structure.
            auto childInstances = docRef->GetDirectChildInstances();
            for (auto &childInstance : childInstances)
            {
                auto childDocRef = childInstance->GetDocumentReference();
                if(!childDocRef)
                {
                    continue;
                }
                // Cash the SPAIopOutputPSReference for tracking the handled output references.
                SPAIopOutputPSReference *childOutputReference = nullptr;
                auto childDocRefId = childDocRef->GetId();
                if (iIdToOutputPSRef_.find(childDocRefId) != iIdToOutputPSRef_.end())
                {
                    childOutputReference = iIdToOutputPSRef_.at(childDocRefId);
                }
                else
                {
                    childOutputReference = new SPAIopOutputPSReference(oOutputPS, childDocRefId);
                    iIdToOutputPSRef_.emplace(childDocRefId, childOutputReference);
                }

                auto transform = childInstance->GetTransform();
                auto childOutputInstance = new SPAIopOutputPSInstance(*childOutputReference);
                auto orientation = transform.GetOrientation();
                double rotation[3][3] = {
                    {orientation[0][0], orientation[0][1], orientation[0][2]},
                    {orientation[1][0], orientation[1][1], orientation[1][2]},
                    {orientation[2][0], orientation[2][1], orientation[2][2]}};
                auto origin = transform.GetOrigin();
                double translation[3] = {origin[0], origin[1], origin[2]};
                childOutputInstance->SetInstancePositionMatrix(rotation, translation);
                childOutputInstance->SetName(StringToSPAIopWString(childInstance->GetName()));
                childOutputInstance->SetPID(StringToSPAIopWString(childInstance->GetPersistName()));
                outputReference.AddInstance(*childOutputInstance);

                ExportInstanceToOutputProductStructureRecursion(childInstance, oOutputPS, *childOutputReference);
            }
        }
    }

    void Writer::ExportOutputProductStructure(const std::shared_ptr<InterOpDocumentInstance> &docRootInstance, SPAIopOutputProductStructure& oOutputPS)
    {
        auto docRef = docRootInstance->GetDocumentReference();
        if (!docRef)
        {
            LOG_ERROR("document reference is null.");
            return;
        }
        // Build child instances and references to output product structure.
        auto childInstances = docRef->GetDirectChildInstances();
        if(childInstances.empty())
        {
            SPAIopAcisOutputPart partData;
            ENTITY_LIST entityList;
            ConvertObject2(docRef, entityList);
            // set the ENTITYs to the Part Data object
            partData.SetPartEntities(entityList);
            auto outputReference = new SPAIopOutputPSReference(oOutputPS, docRef->GetId());
            outputReference->SetOutputPartData(partData);     
            outputReference->SetDefined(true);       

            auto outputInstance = new SPAIopOutputPSInstance(*outputReference);
            outputInstance->SetName(StringToSPAIopWString(docRootInstance->GetName()));
            outputInstance->SetPID(StringToSPAIopWString(docRootInstance->GetPersistName()));
            oOutputPS.AddToRoot(*outputInstance);            
            return;
        }
        for (auto &childInstance : childInstances)
        {
            auto childDocRef = childInstance->GetDocumentReference();
            if(!childDocRef)
            {
                continue;
            }
            auto childDocRefId = childDocRef->GetId();
            SPAIopOutputPSReference* outputReference = nullptr;
            if(iIdToOutputPSRef_.find(childDocRefId) != iIdToOutputPSRef_.end())
            {
                outputReference = iIdToOutputPSRef_.at(childDocRefId);
            }
            else
            {
                outputReference = new SPAIopOutputPSReference(oOutputPS, childDocRefId);
                iIdToOutputPSRef_.emplace(childDocRefId, outputReference);
            }
            auto outputInstance = new SPAIopOutputPSInstance(*outputReference);
            outputInstance->SetName(StringToSPAIopWString(childInstance->GetName()));
            outputInstance->SetPID(StringToSPAIopWString(childInstance->GetPersistName()));
            oOutputPS.AddToRoot(*outputInstance);

            ExportInstanceToOutputProductStructureRecursion(childInstance, oOutputPS, *outputReference);
        }
    }

    void Writer::WriteToFile2(const std::string &filePath, const std::shared_ptr<InterOpDocumentInstance> &docRootInstance)
    {
        auto rootDocRef = docRootInstance->GetDocumentReference();
        if (!rootDocRef)
        {
            LOG_ERROR("root document reference is null.");
            return;
        }

        
        SPAIopOutputProductStructure outputProductStructure;
        outputProductStructure.SetProductName(InterOp::StringToSPAIopWString(rootDocRef->GetName()));
        // TODO: misc data.

        ExportOutputProductStructure(docRootInstance, outputProductStructure);
        SPAIopWString wTargetFilePath = InterOp::StringToSPAIopWString(filePath);
        SPAIopPolicy iExportPolicy;
        SPAIopProductStructureExporter exporter;
        SPAIopResult res = exporter.ExportWithPolicy(iExportPolicy, outputProductStructure, wTargetFilePath);
    }
}

AMCAX_EXCHANGE_NAMESPACE_END